import os
import time

from selenium import webdriver
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait

from utils.models import WebDriver, Locator

POLL_FREQUENCY: float = 0.5


class HtmlPage:
    """Selenium Keywords Function"""

    def __init__(self, driver: WebDriver, time_to_wait: float = 3):
        self.driver = driver
        self.driver.implicitly_wait(time_to_wait)

    def open(self, url: str):
        """Loads a web page in the current browser session.

        :param url:
        :return:
        """
        self.driver.get(url)

    def findElement(self, locator: Locator):
        """Find an element given a locator.

        :param locator:
        :return:
        :rtype: WebElement
        """
        return self.driver.find_element(*locator)

    def text(self, locator: Locator) -> str:
        """The text of the element.

        :param locator:
        :return:
        :rtype: str
        """
        return self.findElement(locator).text

    def input(self, locator: Locator, value):
        """Simulates typing into the element.

        :param locator:
        :param value:
        :return:
        """
        element = self.findElement(locator)
        element.clear()
        element.send_keys(value)

    def submit(self, locator: Locator):
        """Submits a form.

        :param locator:
        :return:
        """
        self.findElement(locator).submit()

    def click(self, locator: Locator):
        """Clicks the element.

        :param locator:
        :return:
        """
        self.findElement(locator).click()

    def asText(self):
        """Gets the source of the current page.

        :return:
        """
        return self.driver.page_source

    def getCurrentUrl(self):
        """Gets the URL of the current page.

        :return:
        """
        return self.driver.current_url

    def getTitleText(self):
        """Returns the title of the current page.

        :return:
        :type: str
        """
        return self.driver.title

    def save_screenshot(self, filename: str | os.PathLike[str]):
        """Saves a screenshot of the current window to a PNG image file.
        Returns False if there is any IOError, else returns True. Use full
        paths in your filename.

        :Args:
         - filename: The full path you wish to save your screenshot to. This
           should end with a `.png` extension.
        """
        self.driver.save_screenshot(filename)

    def get_screenshot_as_base64(self) -> str:
        """Gets the screenshot of the current window as a base64 encoded string
        which is useful in embedded images in HTML.

        :return:
        :rtype: str
        """
        return self.driver.get_screenshot_as_base64()

    def actions(self):
        """Creates a new ActionChains.

        :return:
        """
        return webdriver.ActionChains(self.driver)

    def mouse_hover(self, locator: Locator, seconds: float = 1):
        """Mouse hover

        :param locator:
        :param seconds:
        :return:
        """
        element = self.findElement(locator)
        self.actions().move_to_element(element).pause(seconds).perform()

    @staticmethod
    def waitForPageToload(seconds: float):
        """Delay execution for a given number of seconds.

        :param seconds:
        :return:
        """
        time.sleep(seconds)

    def explicit_wait(self, timeout: float, poll_frequency: float = POLL_FREQUENCY):
        """Explicit wait

        :param timeout: Number of seconds before timing out
        :param poll_frequency: sleep interval between calls
        :return:
        :rtype: WebDriverWait
        """
        return WebDriverWait(self.driver, timeout, poll_frequency)

    def url_changes(self, url: str):
        """An expectation for checking the current url.

        url is the expected url, which must not be an exact match returns
        True if the url is different, false otherwise.

        :param url
        """
        self.explicit_wait(5, 1).until(lambda driver: url != driver.current_url)

    def select_by_value(self, locator: Locator, value: str):
        """Select all options that have a value matching the argument. That is,
        when given "foo" this would select an option like:

        <option value="foo">Bar</option>

        :Args:
         - value - The value to match against

        throws NoSuchElementException If there is no option with specified value in SELECT
        """
        element = self.findElement(locator)
        Select(element).select_by_value(value)

    def close(self):
        """Closes the current window.

        :return:
        """
        self.driver.close()

    def quit(self):
        """Quits the driver and closes every associated window.

        :return:
        """
        self.driver.quit()
